<?php


namespace app\api\model;
use app\common\exception\BaseException;
use app\common\library\AMapModel;
use app\common\library\CoordConv;
use app\common\library\DLock;
use app\common\library\TBitEcuModel;
use app\common\library\TimerALModel;
use app\common\library\XiaoAnEcuModel;
use common;
use hiredis;
use ApiOper;
use app\common\library\himongo;
use think\facade\Log;
use vtimer;
use geovector;

function throw_myexception($err_code, $msg = '') {
    throw new BaseException(['errorCode'=>$err_code, 'msg'=>$msg]);
}

class BikeMgr {

    function check_alert($bike_id) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        if (empty($bike_info))
            return;


        $nowdate = date('Y-m-d H:i:s');
        $bus_update_timestamp = empty($bike_info['bus_update_time']) ? $nowdate : strtotime($bike_info['bus_update_time']);


        //空闲状态锁打开
        if ($bike_info['is_defend_on'] == 0 && $bike_info['bus_state'] == BIKE_BUS_STAT_IDLE) {
            if ($bike_info['inv_state'] != BIKE_INV_STATE_IN_MARKET) {
                $timeout = 1800;
                $defend_update_time = empty($bike_info['defend_update_time']) ? 0 : strtotime($bike_info['defend_update_time']);
                if (time() - $defend_update_time > $timeout) {
                    //(new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_EX_UNLOCK, "车子异常开锁的!");
                    $bound_order_info = [
                        'order_type' => "n",
                        'order_id' => "n",
                        'op' => '异常开锁,自动锁车',
                    ];
                    $this->asyn_lock($bike_info['city_id'], $bike_info['id'], $bound_order_info);

                    //(new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_EX_LONG_UNLOCK, "车子超时未锁");
                }
            } else {
                $timeout = 30;
                if (time() - $bus_update_timestamp > $timeout) {
                    //(new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_EX_UNLOCK, "车子异常开锁的!");
                    if ($bike_info['is_acc_on'] != 0) {
                        $bound_order_info = [
                            'order_type' => "n",
                            'order_id' => "n",
                            'op' => '异常开锁,关闭电门',
                        ];
                        //  $this->asyn_acc_off($bike_info['city_id'], $bike_info['id'], $bound_order_info);
                    }
                    $bound_order_info = [
                        'order_type' => "n1",
                        'order_id' => "n1",
                        'op' => '异常开锁,自动锁车',
                    ];
                    $this->asyn_lock($bike_info['city_id'], $bike_info['id'], $bound_order_info);
                }
            }
        }

        if ($bike_info['inv_state'] == BIKE_INV_STATE_INFO_INIT)
            return;

        $gps_update_timestamp = empty($bike_info['gps_update_time']) ? $nowdate : strtotime($bike_info['gps_update_time']);

        //长时间没有GPS信息
        if (time() - $gps_update_timestamp > ALERT_CONFIG_NO_GSP_MAX) {
            (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_LONG_NO_GPS, "gps久未更新!");
        }

        //长时间没有骑行
        if ($bike_info['bus_state'] == BIKE_BUS_STAT_IDLE && time() - $bus_update_timestamp > ALERT_CONFIG_NO_RIDDEN_MAX) {
            // (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_LONG_NO_BUS, "长时间无人用车!");
        }

        //长时间没有扫码
        $last_scaned_timestamp = empty($bike_info['last_scaned_time']) ? $nowdate : strtotime($bike_info['last_scaned_time']);
        if ($bike_info['bus_state'] == BIKE_BUS_STAT_IDLE && time() - $last_scaned_timestamp > ALERT_CONFIG_NO_SCANED_MAX) {
            (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_LONG_NO_SCANED, "长时间无人扫码!");
        }


        //异常移动,疑似被盗

        if ($bike_info['bus_state'] != BIKE_BUS_STAT_OPER_REPARK && $bike_info['is_in_motion'] && time() - $gps_update_timestamp < 20) {
            $bingo = false;
            if ($bike_info['bus_state'] == BIKE_BUS_STAT_RIDING || $bike_info['bus_state'] == BIKE_BUS_STAT_USER_REPARK) {
                if ($bike_info['speed'] > 40)
                    $bingo = true;
            } else if ($bike_info['speed'] > 20) {
                $bingo = true;
            }

            if ($bingo) {
                $key = 'BIKE_OVER_SPEED_COUNT|' . $bike_info['sn'];
                $c = hiredis::db()->incr($key);
                hiredis::db()->expire($key, 120);
                if ($c > 10) {
                    $bus = '无业务异常移动';
                    if ($bike_info['bus_state'] == BIKE_BUS_STAT_RIDING || $bike_info['bus_state'] == BIKE_BUS_STAT_USER_REPARK) {
                        $bus = '骑行速度异常';
                    } else if ($bike_info['bus_state'] == BIKE_BUS_STAT_OPER_REPARK) {
                        $bus = '挪车速度异常';
                    }
                    (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_STOLEN, "车辆{$bus}，速度{$bike_info['speed']}km/h!");
                }
            }
        }

        //电池锁超时未关
        $battery_lock_update_timestamp = empty($bike_info['battery_lock_update_time']) ? $nowdate : $bike_info['battery_lock_update_time'];
        if ($bike_info['is_battery_locked'] == 0 && time() - strtotime($battery_lock_update_timestamp) > ALERT_CONFIG_BATTERY_UNLOCKED_DURATION_MAX) {
            //(new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_LONG_BATTERY_UNLOCKED, "电池锁超时未关!");
        }

        if ($bike_info['health_state'] == 0 && $bike_info['bus_state'] != BIKE_BUS_STAT_BATTERY_CHANGE && $bike_info['is_under_volt'] == 1) {
            (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_UNDER_VOLT, "电池欠压!");
        }

        //电池空置超时
        $battery_lock_update_timestamp = empty($bike_info['battery_lock_update_time']) ? $nowdate : strtotime($bike_info['battery_lock_update_time']);
        if ($bike_info['is_on_battery'] == 0 && $bike_info['health_state'] == 0 && $bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE && time() - $battery_lock_update_timestamp > ALERT_CONFIG_BATTERY_OFF_DURATION_MAX) {
            //(new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_LONG_BATTERY_OFF, "电池长时间空置!");
        }

        if ($bike_info['bus_state'] == BIKE_BUS_STAT_IDLE && $bike_info['health_state'] == 0 && $bike_info['is_on_battery'] == 0) {
            $key = 'BIKE_EX_BATTERY_OFF_COUNT|' . $bike_info['sn'];
            $c = hiredis::db()->incr($key);
            hiredis::db()->expire($key, 120);
            if ($c > 4) {
                //电池异常空置
                (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_EX_BATTERY_OFF, "电池异常空置!");
            }
        }

        $online_update_time = empty($bike_info['online_update_time']) ? $nowdate : strtotime($bike_info['online_update_time']);
        if ($bike_info['is_on_battery'] == 0 && time() - $online_update_time > ALERT_CONFIG_OFFLINE_MAX) {
            (new BikeAlertModel())->add($bike_info['city_id'], $bike_info['id'], BIKE_ALERT_OFFLINE, "车辆离线!");
        }


        if ($bike_info['is_in_motion'] == 0) {
            if (!empty($bike_info['motion_update_time']) && time() - strtotime($bike_info['motion_update_time']) > AUTO_FINISH_BUS_STATIC_DURATION) {
                if ($bike_info['bus_state'] == BIKE_BUS_STAT_RIDING && time() - strtotime($bike_info['bus_update_time']) >= 900) {
                    //长时间静止车辆，自动结束骑行订单
                    $path = '/urorder/finish';

                    $params = [
                        'token' => 'SUOZHANGBIEKAIQIANG',
                        'city_id' => $bike_info['city_id'],
                        'order_id' => $bike_info['last_order_id'],
                    ];
                    TimerALModel::instance()->call_bus_server($path, $params, 0);
                } else if ($bike_info['bus_state'] == BIKE_BUS_STAT_OPER_REPARK && time() - strtotime($bike_info['bus_update_time']) > 3 * AUTO_FINISH_BUS_STATIC_DURATION) {
                    //长时间静止车辆，自动结束挪车订单
                    $path = '/rporder/finish';

                    $params = [
                        'token' => 'SUOZHANGBIEKAIQIANG',
                        'city_id' => $bike_info['city_id'],
                        'order_id' => $bike_info['last_repark_order_id'],
                        'user_coordinate' => [0, 0],
                        'park_id' => '0',
                    ];
                    TimerALModel::instance()->call_bus_server($path, $params, 0);
                } else if ($bike_info['bus_state'] == BIKE_BUS_STAT_USER_REPARK && time() - strtotime($bike_info['bus_update_time']) > 5*60) {
                    //长时间静止车辆，自动结束用户挪车订单
                    $path = '/urorder/user_repark_finish';

                    $params = [
                        'token' => 'SUOZHANGBIEKAIQIANG',
                        'city_id' => $bike_info['city_id'],
                        'order_id' => $bike_info['last_repark_order_id'],
                        'user_coordinate' => [0, 0],
                        'park_id' => '0',
                    ];
                    TimerALModel::instance()->call_bus_server($path, $params, 0);
                }
            }
        }
        if ($bike_info['bus_state'] == BIKE_BUS_STAT_TEMP_PARK && time() - strtotime($bike_info['bus_update_time']) > AUTO_FINISH_BUS_STATIC_DURATION * 6) {
            //长时间静止车辆，自动结束骑行订单
            $path = '/urorder/finish';

            $params = [
                'token' => 'SUOZHANGBIEKAIQIANG',
                'city_id' => $bike_info['city_id'],
                'order_id' => $bike_info['last_order_id'],
            ];
            TimerALModel::instance()->call_bus_server($path, $params, 0);
        }
        if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE && time() - strtotime($bike_info['last_battery_change_order_start_time']) > AUTO_FINISH_BUS_STATIC_DURATION) {
            //长时间静止车辆，自动结束挪车订单
            $path = '/bcorder/finish';

            $params = [
                'token' => 'SUOZHANGBIEKAIQIANG',
                'city_id' => $bike_info['city_id'],
                'order_id' => $bike_info['last_battery_change_order_id'],
                'user_coordinate' => [0, 0],
            ];
            TimerALModel::instance()->call_bus_server($path, $params, 0);
        }

        //用户挪车
        if ($bike_info['bus_state'] == BIKE_BUS_STAT_USER_REPARK) {
            if(time() - strtotime($bike_info['last_repark_order_start_time']) >= 180){
                $path = '/urorder/user_repark_finish';

                $params = [
                    'token' => 'SUOZHANGBIEKAIQIANG',
                    'city_id' => $bike_info['city_id'],
                    'order_id' => $bike_info['last_repark_order_id'],
                    'user_coordinate' => [0, 0],
                    'park_id' => '0',
                ];
                TimerALModel::instance()->call_bus_server($path, $params, 0);
            }
            else if (time() - strtotime($bike_info['last_repark_order_start_time']) >= 115){
                if ($bike_info['is_acc_on'] == 1) {
                    $bound_order_info = [
                        'order_type' => ORDER_TYPE_USER_REPARK,
                        'order_id' => $bike_info['last_repark_order_id'],
                        'op' => '用户挪车二分钟断电',
                    ];
                    $this->asyn_acc_off($bike_info['city_id'], $bike_info['id'], $bound_order_info);
                }
            }
        }

        if ($bike_info['bus_state'] != BIKE_BUS_STAT_IDLE && time() - strtotime($bike_info['bus_update_time']) > 3600 * 24) {
            (new BikeModel())->set_bus_state_idle($bike_info['city_id'], $bike_info['id']);
        }

        $alert_type_arr = [
        ];

        if ($bike_info['is_online'] == 1) {
            $alert_type_arr[] = BIKE_ALERT_OFFLINE;
        }
        if (!empty($bike_info['coordinate']) && $bike_info['locate_type'] == 'gps' && time() - $gps_update_timestamp < 60) {
            $alert_type_arr[] = BIKE_ALERT_GPS_FAILED;
            $alert_type_arr[] = BIKE_ALERT_LONG_NO_GPS;
        }

        if ($bike_info['is_under_volt'] == 0 && $bike_info['battery_level'] > 10) {
            $alert_type_arr[] = BIKE_ALERT_UNDER_VOLT;
        }

        if ($bike_info['is_defend_on'] == 1) {
            $alert_type_arr[] = BIKE_ALERT_EX_UNLOCK;
            $alert_type_arr[] = BIKE_ALERT_EX_LONG_UNLOCK;
            $alert_type_arr[] = BIKE_ALERT_LOCK_FAILED;
        }

        if ($bike_info['is_battery_locked'] == 1) {
            $alert_type_arr[] = BIKE_ALERT_LONG_BATTERY_UNLOCKED;
        }

        if ($bike_info['is_on_battery'] == 1) {
            $alert_type_arr[] = BIKE_ALERT_LONG_BATTERY_OFF;
            $alert_type_arr[] = BIKE_ALERT_EX_BATTERY_OFF;
        }

        if (time() - $last_scaned_timestamp < 60) {
            $alert_type_arr[] = BIKE_ALERT_LONG_NO_SCANED;
        }

        if (time() - $bus_update_timestamp < ALERT_CONFIG_NO_RIDDEN_MAX) {
            $alert_type_arr[] = BIKE_ALERT_LONG_NO_BUS;
        }

        if ($bike_info['bus_state'] >= BIKE_BUS_STAT_OPER_REPARK) {
            $alert_type_arr[] = BIKE_ALERT_STOLEN;
            $alert_type_arr[] = BIKE_ALERT_FAULT;
        }

        if (!empty($alert_type_arr)) {
            $this->auto_handle_alert($bike_info['city_id'], $bike_info['id'], $alert_type_arr);
        }
    }

    function oper_repark_start($city_id, $bike_id, $order_info, $bluetooth, $unlock) {
        $bound_order_info = [
            'order_type' => ORDER_TYPE_BIKE_REPARK,
            'order_id' => $order_info['id'],
            'op' => '挪车开锁',
        ];

        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);

        if ($unlock && !$bluetooth) {
            $ret = $this->unlock($city_id, $bike_info['id'], $bound_order_info);
            if ($ret[0] != 0) {
                throw_myexception(ERROR_LOCK_ERROR, "开锁失败!");
            }
        }

        if (!empty($bike_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $bike_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $bike_info['park_id'], $park_info['bike_count'] - 1, -1);
            }
        }
        if (!empty($bike_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $bike_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $bike_info['park_id'], $npa_info['bike_count'] - 1, -1);
            }
        }

        BikeModel::instance()->oper_repark_start($city_id, $bike_id, $order_info);
        if ($unlock && $bluetooth) {
            $bound_order_info = [
                'order_type' => ORDER_TYPE_USER_RIDE,
                'order_id' => $order_info['id'],
                'op' => '挪车开锁',
            ];

            //TODO
            $this->asyn_unlock($city_id, $bike_id, $bound_order_info);
            $this->asyn_check_defend_state($bike_id);
        }
    }

    function oper_repark_finish($city_id, $bike_id, $add_park_info) {
        if (!empty($add_park_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $add_park_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $add_park_info['park_id'], $park_info['bike_count'] + 1, 1);
            }
        }
        if (!empty($add_park_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $add_park_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $add_park_info['npa_id'], $npa_info['bike_count'] + 1, 1);
            }
        }
        (new BikeModel())->oper_repark_finish($city_id, $bike_id, $add_park_info);

        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
    }


    function user_repark_start($city_id, $bike_id, $order_info, $bluetooth, $unlock) {
        $bound_order_info = [
            'order_type' => ORDER_TYPE_USER_REPARK,
            'order_id' => $order_info['id'],
            'op' => '用户挪车开锁',
        ];

        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);

        if ($unlock && !$bluetooth) {
            $ret = $this->unlock($city_id, $bike_info['id'], $bound_order_info);
            if ($ret[0] != 0) {
                throw_myexception(ERROR_LOCK_ERROR, "开锁失败!");
            }
        }

        if (!empty($bike_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $bike_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $bike_info['park_id'], $park_info['bike_count'] - 1, -1);
            }
        }
        if (!empty($bike_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $bike_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $bike_info['park_id'], $npa_info['bike_count'] - 1, -1);
            }
        }

        BikeModel::instance()->user_repark_start($city_id, $bike_id, $order_info);
        if ($unlock && $bluetooth) {
            $bound_order_info = [
                'order_type' => ORDER_TYPE_USER_REPARK,
                'order_id' => $order_info['id'],
                'op' => '用户挪车开锁',
            ];

            //TODO
            $this->asyn_unlock($city_id, $bike_id, $bound_order_info);
            $this->asyn_check_defend_state($bike_id);
        }
    }

    function user_repark_finish($city_id, $bike_id, $add_park_info) {
        if (!empty($add_park_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $add_park_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $add_park_info['park_id'], $park_info['bike_count'] + 1, 1);
            }
        }
        if (!empty($add_park_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $add_park_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $add_park_info['npa_id'], $npa_info['bike_count'] + 1, 1);
            }
        }
        (new BikeModel())->user_repark_finish($city_id, $bike_id, $add_park_info);

        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
    }

    function user_order_start($city_id, $bike_id, $order_info, $bluetooth) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        if (!$bluetooth) {
            $bound_order_info = [
                'order_type' => ORDER_TYPE_USER_RIDE,
                'order_id' => $order_info['id'],
                'op' => '骑行开锁',
            ];


            $ret = $this->unlock($city_id, $bike_info['id'], $bound_order_info);
            if ($ret[0] != 0) {
                throw_myexception(ERROR_LOCK_ERROR, "开锁失败!");
            }
        }

        if (!empty($bike_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $bike_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $bike_info['park_id'], $park_info['bike_count'] - 1, -1);
            }
        }
        if (!empty($bike_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $bike_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $bike_info['park_id'], $npa_info['bike_count'] - 1, -1);
            }
        }
        (new BikeModel())->user_order_start($city_id, $bike_id, $order_info);
        (new UserModel())->update_last_order_time($city_id, $order_info['user_id']);

        if ($bluetooth) {
            $bound_order_info = [
                'order_type' => ORDER_TYPE_USER_RIDE,
                'order_id' => $order_info['id'],
                'op' => '骑行开锁',
            ];

            //TODO
            //$this->asyn_unlock($city_id, $bike_id, $bound_order_info);
            $this->asyn_check_defend_state($bike_id);
        }
    }

    function user_order_reserve($city_id, $bike_id, $order_info) {
        (new BikeModel())->user_order_reserve($city_id, $bike_id, $order_info);
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);

        if (!empty($bike_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $bike_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $bike_info['park_id'], $park_info['bike_count'] - 1, -1);
            }
        }
        if (!empty($bike_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $bike_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $bike_info['park_id'], $npa_info['bike_count'] - 1, -1);
            }
        }
    }

    function user_order_reride($city_id, $bike_info, $order_info, $bluetooth) {
        $ret = [0, 'OK', 0];
        if ($bluetooth == 0) {
            $bound_order_info = [
                'order_type' => ORDER_TYPE_USER_RIDE,
                'order_id' => $order_info['id'],
                'op' => '继续骑行开锁',
            ];

            $ret = $this->unlock($city_id, $bike_info['id'], $bound_order_info);
            if ($ret[0] != 0) {
                return $ret;
            }
        }

        (new BikeModel())->user_order_reride($city_id, $bike_info, $order_info);
        return $ret;
    }

    function user_order_temp_park($city_id, $bike_info, $order_info, $bluetooth) {
        $ret = [0, 'OK', 0];
        if ($bluetooth == 0) {
            $bound_order_info = [
                'order_type' => ORDER_TYPE_USER_RIDE,
                'order_id' => $order_info['id'],
                'op' => '临时停车关锁',
            ];

            $ret = $this->temp_lock($city_id, $bike_info['id'], $bound_order_info);
            if ($ret[0] != 0) {
                return $ret;
            }
        }

        (new BikeModel())->user_order_temp_park($city_id, $bike_info, $order_info);
        return $ret;
    }

    function user_order_finish($city_id, $bike_id, $add_park_info,$distance,$is_flush =0) {
        if (!empty($add_park_info['park_id'])) {
            $park_info = (new ParkModel())->info($city_id, $add_park_info['park_id']);
            if (!empty($park_info)) {
                (new ParkModel())->incr_bike_count($city_id, $add_park_info['park_id'], $park_info['bike_count'] + 1, 1);
            }
        }
        if (!empty($add_park_info['npa_id'])) {
            $npa_info = (new NPAModel())->info($city_id, $add_park_info['npa_id']);
            if (!empty($npa_info)) {
                (new NPAModel())->incr_bike_count($city_id, $add_park_info['npa_id'], $npa_info['bike_count'] + 1, 1);
            }
        }

        BikeModel::instance()->user_order_finish($city_id, $bike_id, $add_park_info,$distance,$is_flush);
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
    }

    function update_address($city_id, $bike_id, $address) {
        (new BikeModel())->update_address($city_id, $bike_id, $address);
    }

    static function voltmv_to_percent($mv) {
        $p = floor(100 * ($mv - 3366 * 13) / (839 * 13));
        if ($p < 0) {
            $p = 0;
        }
        if ($p > 100) {
            $p = 100;
        }
        return $p;
    }

    static function voltmv_to_percent_v2($mv) {
        $arr = [
            49.7,
            48.7,
            48.7,
            48.7,
            48.6,
            48.5,
            48.4,
            48.3,
            48.2,
            48.1,
            48.0,
            47.9,
            47.8,
            47.7,
            47.5,
            47.3,
            46.9,
            46.5,
            46.0,
            44.0,
            32.7,
        ];

        for ($i = 0; $i < count($arr); $i++) {
            if ($mv > $arr[$i] * 1000) {
                return 100 - $i * 5;
            }
        }
        return 0;
    }

    //TN
    static function voltmv_to_percent_TN($mv_acc_off, $mv_acc_on)
    {
//        $arr = [
//            53.8,
//            52.1,
//            51.2,
//            50.5,
//            49.7,
//            49.1,
//            48.4,
//            47.7,
//            47.2,
//            46.8,
//            46.5,
//            46.2,
//            46,
//            45.7,
//            45.5,
//            45.2,
//            44.9,
//            44.5,
//            43.9,
//            43.1,
//            38.7,
//        ];

        //0度

        $arr = null;

        $level_acc_off = -1;
        $level_acc_on = -1;


        if ($mv_acc_on >= 0) {
            $arr = [
                52148 => 100,
                51332 => 95,
                50626 => 80,
                49920 => 75,
                49242 => 70,
                48576 => 65,
                47968 => 60,
                47434 => 55,
                47008 => 50,
                46666 => 45,
                46374 => 40,
                46106 => 35,
                45846 => 30,
                45592 => 25,
                45316 => 20,
                44984 => 15,
                44564 => 10,
                43914 => 5,
                38890 => 0,
                0 =>0
            ];
            foreach ($arr as $k => $v) {
                if ($mv_acc_on >= $k) {
                    $level_acc_on = $v;
                    break;
                }
            }
        }



        if ($mv_acc_off >= 0) {

            $arr = [
                54338 => 100,
                52148 => 95,
                51332 => 90,
                50626 => 85,
                49920 => 80,
                49242 => 75,
                48576 => 70,
                47968 => 65,
                47434 => 60,
                47008 => 55,
                46666 => 50,
                46374 => 45,
                46106 => 40,
                45846 => 35,
                45592 => 30,
                45316 => 25,
                44984 => 20,
                44564 => 15,
                43914 => 10,
                42978 => 5,
                38890 => 0,
                0 =>0
            ];


            foreach ($arr as $k => $v) {
                if ($mv_acc_off >= $k) {
                    $level_acc_off = $v;
                    break;
                };
            }
        }

        if ($level_acc_on >= 0 && $level_acc_off >= 0) {
            return floor($level_acc_off * 0.3 + $level_acc_on * 0.7);
        } else {
            return max($level_acc_off, $level_acc_on);
        }

        return 100;
    }

    static function voltmv_to_percent_TN15A($mv_acc_off, $mv_acc_on)
    {
//        $arr = [
//            53.8,
//            52.1,
//            51.2,
//            50.5,
//            49.7,
//            49.1,
//            48.4,
//            47.7,
//            47.2,
//            46.8,
//            46.5,
//            46.2,
//            46,
//            45.7,
//            45.5,
//            45.2,
//            44.9,
//            44.5,
//            43.9,
//            43.1,
//            38.7,
//        ];

        //0度

        $arr = null;

        $level_acc_off = -1;
        $level_acc_on = -1;


        if ($mv_acc_on >= 0) {
            $arr = [
                50267 => 100,
                49912 => 90,
                49586 => 80,
                49218 => 75,
                49050 => 70,
                48516 => 60,
                48086 => 50,
                47575 => 40,
                47084 => 30,
                46475 => 25,
                46067 => 20,
                45656 => 10,
                44704 => 5,
                42805 => 0,
            ];
            foreach ($arr as $k => $v) {
                if ($mv_acc_on >= $k) {
                    $level_acc_on = $v;
                    break;
                }
            }
        }



        if ($mv_acc_off >= 0) {

            $arr = [
                52715 => 100,
                51742 => 95,
                50847 => 90,
                50033 => 85,
                49157 => 80,
                48542 => 75,
                47976 => 70,
                47482 => 65,
                47076 => 60,
                46740 => 55,
                46460 => 50,
                46216 => 45,
                45990 => 40,
                45762 => 35,
                45527 => 30,
                44943 => 25,
                44496 => 20,
                43899 => 15,
                43270 => 10,
                41744 => 5,
                35943 => 0,
                0 =>0
            ];


            foreach ($arr as $k => $v) {
                if ($mv_acc_off >= $k) {
                    $level_acc_off = $v;
                    break;
                };
            }
        }

        if ($level_acc_on >= 0 && $level_acc_off >= 0) {
            return floor($level_acc_off * 0.3 + $level_acc_on * 0.7);
        } else {
            return max($level_acc_off, $level_acc_on);
        }

        return 100;
    }

    function query_bike_info_rt($imei) {
        $bike_info = BikeModel::instance()->get_info_by_imei($imei);
        $info = null;
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            (new TBitEcuModel())->locate_rt($bike_info['ecu_sn']);
        } else {
            $info = (new XiaoAnEcuModel())->query_device_info_rt($imei);
            if (!empty($info)) {
                $this->on_bike_info_update($imei, $info);
            }
        }

        $bike_info = BikeModel::instance(true)->get_info_by_imei($imei);
        return $bike_info;
    }

    function query_bike_tbt($bike_info) {
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            (new TBitEcuModel())->locate_rt($bike_info['ecu_sn']);
        }
    }

    function set_alert($city_id, $bike_id, $alert_type, $alert_content) {
        BikeModel::instance()->set_alert($city_id, $bike_id, $alert_type);
    }

    function set_health_state($city_id, $bike_id, $health_state, $health_desc) {
        BikeModel::instance()->set_health_state($city_id, $bike_id, $health_state, $health_desc);
    }

    function recall($city_id, $bike_id, $warehouse_id, $warehouse_name) {
        BikeModel::instance()->bus_inv_recall($city_id, $bike_id, $warehouse_id, $warehouse_name);
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        $info = [
            'city_id' => $city_id,
            'bike_id' => $bike_id,
            'bike_sn' => $bike_info['sn'],
            'warehouse_id' => $warehouse_id,
            'warehouse_name' => $warehouse_name,
            'imei' => $bike_info['imei'],
            'inv_state' => BIKE_INV_STATE_RECALL,
        ];
        (new BikeWhRecordModel())->add($city_id, BIKE_WH_RECORD_TYPE_RECALL, $bike_id, $warehouse_id, $warehouse_name);
    }

    function add_bike($bike_info) {
        $bike_info = BikeModel::instance()->add($bike_info);
        (new BikeWhRecordModel())->add($bike_info['city_id'], 0, $bike_info['id'], $bike_info['warehouse_id'], $bike_info['warehouse_name']);
    }

    function launch_to_market($city_id, $bike_id) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        if ($bike_info['inv_state'] != BIKE_INV_STATE_INFO_INIT && $bike_info['inv_state'] != BIKE_INV_STATE_DELIVING) {
            throw_myexception(ERROR_STATE_DISMATCH, "车辆已投放");
        }

        (new BikeWhRecordModel())->add($city_id, BIKE_WH_RECORD_TYPE_LAUNCH, $bike_info['id'], $bike_info['warehouse_id'], $bike_info['warehouse_name']);

        $bike_info = BikeModel::instance()->launch_to_market($city_id, $bike_id);

        (new BikeWhRecordModel())->add($city_id, BIKE_WH_RECORD_TYPE_LAUNCH, $bike_info['id'], $bike_info['warehouse_id'], $bike_info['warehouse_name']);
    }

    function deliver($city_id, $bike_id) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        if ($bike_info['inv_state'] != BIKE_INV_STATE_INFO_INIT) {
            throw_myexception(ERROR_STATE_DISMATCH, "车辆已投放");
        }
        (new BikeWhRecordModel())->add($city_id, BIKE_WH_RECORD_TYPE_DELIVER, $bike_info['id'], $bike_info['warehouse_id'], $bike_info['warehouse_name']);
        BikeModel::instance()->deliver($bike_id);
    }

    function temp_lock($city_id, $bike_id, $bound_order_info) {
        $dlock = new DLock("bike_defend_lock_" . $bike_id, 30);
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);

        $ret = [-1, "未知型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $ret = (new TBitEcuModel())->temp_lock($bike_info['ecu_sn']);
        } else {
            $ret = (new XiaoAnEcuModel())->xiao_an_ecu_lock($bike_info['imei']);
        }

        if ($ret[0] === 0) {
            $info = [
                'is_defend_on' => 1,
                'is_acc_on' => 0,
                'defend_update_time' => date('Y-m-d H:i:s'),
            ];
            BikeModel::instance()->on_bike_info_update($city_id, $bike_id, $info);
        } else if ($ret[0] === ECU_ERROR_IN_MOTION) {
            $this->asyn_acc_off($bike_info['city_id'], $bike_info['id'], $bound_order_info);
        }

        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_LOCK, $city_id, $bike_id, $bound_order_info, $ret);
        return $ret;
    }

    function lock($city_id, $bike_id, $bound_order_info) {
        $dlock = new DLock("bike_defend_lock_" . $bike_id, 30);
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);

        $ret = [-1, "未知型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $ret = (new TBitEcuModel())->lock($bike_info['ecu_sn']);
        } else {
            $ret = (new XiaoAnEcuModel())->xiao_an_ecu_lock($bike_info['imei']);
        }

        if ($ret[0] === 0) {
            $info = [
                'is_defend_on' => 1,
                'is_acc_on' => 0,
                'defend_update_time' => date('Y-m-d H:i:s'),
            ];
            BikeModel::instance()->on_bike_info_update($city_id, $bike_id, $info);
        } else if ($ret[0] === ECU_ERROR_IN_MOTION) {
            $this->asyn_acc_off($bike_info['city_id'], $bike_info['id'], $bound_order_info);
        }

        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_LOCK, $city_id, $bike_id, $bound_order_info, $ret);
        return $ret;
    }

    function unlock($city_id, $bike_id, $bound_order_info) {
        $dlock = new DLock("bike_defend_lock_" . $bike_id, 30);
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        $ret = [-1, "未知设备型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $ret = (new TBitEcuModel())->unlock($bike_info['ecu_sn']);
        } else {
            $ret = (new XiaoAnEcuModel())->xiao_an_ecu_unlock($bike_info['imei']);
        }

        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_UNLOCK, $bike_info['city_id'], $bike_id, $bound_order_info, $ret);
        if ($ret[0] != 0 && in_array($bike_info['bus_state'], [BIKE_BUS_STAT_IDLE, BIKE_BUS_STAT_TEMP_PARK, BIKE_BUS_STAT_RESERVED])) {
            $content = "开锁失败, code=" . $ret[0] . ", msg=" . $ret[1] . ' cost=' . $ret[2];
            //(new BikeAlertModel())->add($city_id, $bike_info['id'], BIKE_ALERT_LOCK_FAILED, $content);
        } else {

        }

        if ($ret[0] === 0) {
            $info = [
                'is_defend_on' => 0,
                'defend_update_time' => date('Y-m-d H:i:s'),
                'is_acc_on' => 1,
            ];
            BikeModel::instance()->on_bike_info_update($bike_info['city_id'], $bike_id, $info);
        }
        return $ret;
    }

    function asyn_lock($city_id, $bike_id, $bound_order_info, $timeout = 0) {
        $params = [
            'city_id' => $city_id,
            'bike_id' => $bike_id,
            'bound_order_type' => $bound_order_info['order_type'],
            'bound_order_id' => $bound_order_info['order_id'],
            'bound_order_op' => $bound_order_info['op'],
            'user_coordinate' => [0, 0],
            'token' => 'SUOZHANGBIEKAIQIANG',
        ];

        TimerALModel::instance()->call_bus_server('/bike/lock', $params, $timeout);
    }

    function asyn_update_bluetooth($bike_info) {
        if (empty($bike_info['bluetooth_token'])) {
            $params = [
                'bike_sn' => $bike_info['sn'],
                'token' => 'SUOZHANGBIEKAIQIANG',
            ];

            TimerALModel::instance()->call_bus_server('/backend/update_bike_bluetooth', $params, 0);
        }
    }

    function asyn_unlock($city_id, $bike_id, $bound_order_info) {
        $params = [
            'city_id' => $city_id,
            'bike_id' => $bike_id,
            'bound_order_type' => $bound_order_info['order_type'],
            'bound_order_id' => $bound_order_info['order_id'],
            'bound_order_op' => $bound_order_info['op'],
            'user_type' => ApiOper::instance()->userinfo()['user_type'],
            'user_id' => ApiOper::instance()->userinfo()['id'],
            'token' => 'SUOZHANGBIEKAIQIANG',
        ];

        TimerALModel::instance()->call_bus_server('/bike/unlock', $params, 5);
    }

    function asyn_check_defend_state($bike_id) {
        $params = [
            'bike_id' => $bike_id,
            'token' => 'SUOZHANGBIEKAIQIANG',
        ];

        TimerALModel::instance()->call_bus_server('/bike/check_defend_state', $params, 5);
    }

    function asyn_unlock_battery($city_id, $bike_id, $bound_order_info) {
        $params = [
            'city_id' => $city_id,
            'id' => $bike_id,
            'bound_order_type' => $bound_order_info['order_type'],
            'bound_order_id' => $bound_order_info['order_id'],
            'bound_order_op' => $bound_order_info['op'],
            'user_type' => ApiOper::instance()->userinfo()['user_type'],
            'user_id' => ApiOper::instance()->userinfo()['id'],
            'token' => 'SUOZHANGBIEKAIQIANG',
        ];

        TimerALModel::instance()->call_bus_server('/bike/unlock_battery', $params, 0);
    }

    function asyn_device_voice($city_id, $bike_id, $type, $bound_order_info) {
        $params = [
            'city_id' => $city_id,
            'bike_id' => $bike_id,
            'type' => $type,
            'bound_order_info' => $bound_order_info,
        ];

        TimerALModel::instance()->call_bus_server('/bike/voice', $params, 0);
    }

    function acc_on($city_id, $bike_id, $bound_order_info) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        $ret = [-2, "未知设备型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $ret = (new TBitEcuModel())->acc_on($bike_info['ecu_sn']);
        } else {
            $ret = (new XiaoAnEcuModel())->xiao_an_ecu_acc_on($bike_info['imei']);
        }

        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_ACC_ON, $city_id, $bike_id, $bound_order_info, $ret);
        if ($ret[0] === 0) {
            $info = [
                'is_acc_on' => 1,
            ];
            BikeModel::instance()->on_bike_info_update($city_id, $bike_id, $info);
        }
        return $ret[0];
    }

    function acc_off($city_id, $bike_id, $bound_order_info) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        $ret = [-2, "未知设备型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $ret = (new TBitEcuModel())->acc_off($bike_info['ecu_sn']);
        } else {
            $ret = (new XiaoAnEcuModel())->xiao_an_ecu_acc_off($bike_info['imei']);
        }

        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_ACC_OFF, $city_id, $bike_id, $bound_order_info, $ret);
        if ($ret[0] == 0) {
            $info = [
                'is_acc_on' => 0,
            ];
            BikeModel::instance()->on_bike_info_update($city_id, $bike_id, $info);
        }
        return $ret[0];
    }

    function asyn_acc_on($city_id, $bike_id, $bound_order_info) {

        $params = [
            'city_id' => $city_id,
            'bike_id' => $bike_id,
            'bound_order_type' => $bound_order_info['order_type'],
            'bound_order_id' => $bound_order_info['order_id'],
            'bound_order_op' => $bound_order_info['op'],
            'token' => 'SUOZHANGBIEKAIQIANG',
        ];


        TimerALModel::instance()->call_bus_server('/bike/acc_on', $params, 0);
    }

    function asyn_acc_off($city_id, $bike_id, $bound_order_info) {
        $params = [
            'city_id' => $city_id,
            'bike_id' => $bike_id,
            'bound_order_type' => $bound_order_info['order_type'],
            'bound_order_id' => $bound_order_info['order_id'],
            'bound_order_op' => $bound_order_info['op'],
            'token' => 'SUOZHANGBIEKAIQIANG',
        ];

        TimerALModel::instance()->call_bus_server('/bike/acc_off', $params, 0);
    }

    function on_xiaoan_ecu_event_ping($imei, $data) {
        $bike_info = BikeModel::instance()->get_info_by_imei($imei);
        $info = [
            'online_update_time' => date('Y-m-d H:i:s'),
            'is_online' => 1,
            'gsm_signal_strength' => $data['gsm'],
        ];
        $this->on_bike_info_update($imei, $info);
    }

    function on_tbit_ecu_event_ping($ecu_sn, $data) {
        $bike_info = BikeModel::instance()->get_info_by_ecu_sn($ecu_sn);
        if (empty($bike_info)) {
            return;
        }

        $bike_state = $data['state'];
        $info = [
            'is_online' => 1,
            'online_update_time' => date('Y-m-d H:i:s', $data['timestamp']),
            'battery_volt' => intval($data['battery_volt']),
            'is_defend_on' => $bike_state['is_defend_on'],
            'is_acc_on' => $bike_state['is_acc_on'],
            'is_wheel_locked' => $bike_state['is_wheel_locked_acc'],
            'is_sleeping' => $bike_state['is_sleeping'],
            'is_in_motion' => $bike_state['is_in_motion'],
        ];

        $this->on_bike_info_update($bike_info['imei'], $info);
    }

    function on_bike_info_update($imei, $data) {
        $bike_info = BikeModel::instance()->get_info_by_imei($imei);
        if (empty($bike_info)) {
            return;
        }
        $info = [
        ];

        $info = array_merge($info, $data);

        $update_time = time();

        if (isset($info['is_defend_on'])) {
            if ($info['is_defend_on'] != $bike_info['is_defend_on']) {
                $info['defend_update_time'] = date('Y-m-d H:i:s');
            }
        }

        if (isset($info['is_battery_locked']) && $info['is_battery_locked'] != $bike_info['is_battery_locked']) {
            $info['battery_lock_update_time'] = date('Y-m-d H:i:s');
        }


        if (isset($data['is_in_motion'])) {
            if ($info['is_in_motion'] > 0 || ($data['is_in_motion'] != $bike_info['is_in_motion'])) {
                $info['motion_update_time'] = date('Y-m-d H:i:s');
            }
        } else if ($bike_info['is_in_motion'] == 1) {
            $motion_update_time = 0;
            if (!empty($bike_info['motion_update_time'])) {
                $motion_update_time = strtotime($bike_info['motion_update_time']);
            }
            if (time() - $motion_update_time > 600) {
                $info['is_in_motion'] = 0;
                $info['motion_update_time'] = date('Y-m-d H:i:s');
            }
        }


        if (!isset($info['is_online'])) {
            $online_update_time = empty($bike_info['online_update_time']) ? 0 : strtotime($bike_info['online_update_time']);
            if ($bike_info['is_online'] == 1 && time() - $online_update_time > 1800) {
                $info = [
                    'is_online' => 0,
                    'online_update_time' => date('Y-m-d H:i:s'),
                ];
            }
        }


        if (isset($info['coordinate'])) {
            if (!empty($bike_info['gps_update_time']) && strtotime($bike_info['gps_update_time']) > strtotime($info['gps_update_time'])) {
                unset($info['coordinate']);
                unset($info['locate_type']);
                unset($info['speed']);
                unset($info['hdop']);
                unset($info['course']);
            }
        }

        if (isset($info['coordinate'])) {
            $coordinate = $info['coordinate'];

            $distance = common::distance($bike_info['coordinate'], $info['coordinate']);
            if (abs($distance) > 6) {
                $info['address'] = (new AMapModel())->map_c_2_a($bike_info['coordinate']);
            }

            if ($info['speed'] > 0 && !empty($bike_info['gps_update_time']) && !empty($info['gps_update_time'])) {
                $t = strtotime($info['gps_update_time']) - strtotime($bike_info['gps_update_time']);
                if ($t > 0) {
                    $speed = floor($distance / $t * 3.6);
                    $info['speed'] = min($info['speed'], $speed);
                } else {
                    $info['speed'] = 0;
                }
            }

            if ($bike_info['bus_state'] == BIKE_BUS_STAT_IDLE) {
                $bingo = false;
                $park_update_time = 0;
                if (!empty($bike_info['park_update_time'])) {
                    $park_update_time = strtotime($bike_info['park_update_time']);
                }
                if (time() - $park_update_time > 600) {
                    $bingo = true;
                }
                if (!$bingo) {
                    $distance = common::distance($bike_info['park_coordinate'], $info['coordinate']);
                    if ($distance > 30) {
                        $bingo = true;
                    }
                }
                if ($bingo) {
                    $parkinfo = (new ParkModel())->is_in($bike_info['city_id'], $coordinate);
                    $npkinfo = (new NPAModel())->is_in($bike_info['city_id'], $coordinate);
                    $in_srv_area = (new ServiceAreaModel())->is_in($bike_info['city_id'], $coordinate);
                    if (common::safe_index($parkinfo, 'id') != $bike_info['park_id'] || common::safe_index($npkinfo, 'id') != $bike_info['npa_id'] || common::safe_index($in_srv_area, 'id') != $bike_info['svca_id']) {
                        $info['park_state'] = BIKE_PARK_STAT_FREE;
                        $info['svca_id'] = null;
                        $info['svca_name'] = null;
                        $info['npa_id'] = null;
                        $info['npa_name'] = null;
                        $info['park_id'] = null;
                        $info['park_name'] = null;
                        $info['park_update_time'] = date('Y-m-d H:i:s');
                        $info['park_coordinate'] = $coordinate;

                        if ($parkinfo) {
                            $info['park_state'] = BIKE_PARK_STAT_PARK;
                            $info['park_id'] = $parkinfo['id'];
                            $info['park_name'] = $parkinfo['name'];
                        }

                        if ($npkinfo) {
                            $info['park_state'] = BIKE_PARK_STAT_NPA;
                            $info['npa_id'] = $npkinfo['id'];
                            $info['npa_name'] = $npkinfo['name'];
                        }
                        if (!empty($in_srv_area)) {
                            $info['svca_id'] = $in_srv_area['id'];
                            $info['svca_name'] = $in_srv_area['name'];
                        } else {
                            $info['park_state'] = BIKE_PARK_STAT_OSA;
                        }

                        if ($bike_info['park_id'] != $info['park_id']) {
                            if ($bike_info['park_id']) {
                                $old_park_info = (new ParkModel())->info($bike_info['city_id'], $bike_info['park_id']);
                                if ($old_park_info) {
                                    (new ParkModel())->incr_bike_count($bike_info['city_id'], $old_park_info['id'], $old_park_info['bike_count'] - 1, -1);
                                }
                            }
                            if ($parkinfo) {
                                (new ParkModel())->incr_bike_count($bike_info['city_id'], $parkinfo['id'], $parkinfo['bike_count'] + 1, 1);
                            }
                        }

                        if ($bike_info['npa_id'] != $info['npa_id']) {
                            if ($bike_info['npa_id']) {
                                $old_npa_info = (new NPAModel())->info($bike_info['city_id'], $bike_info['npa_id']);
                                if ($old_npa_info) {
                                    (new NPAModel())->incr_bike_count($bike_info['city_id'], $old_npa_info['id'], $old_npa_info['bike_count'] - 1, -1);
                                }
                            }
                            if ($npkinfo) {
                                (new NPAModel())->incr_bike_count($bike_info['city_id'], $npkinfo['id'], $npkinfo['bike_count'] + 1, 1);
                            }
                        }
                    }
                }
            }

            if ($bike_info['bus_state'] == BIKE_BUS_STAT_RIDING) {
                $in_srv_area = (new ServiceAreaModel())->is_in($bike_info['city_id'], $coordinate);
                $in_np = null;
                if (!empty($in_srv_area)) {
                    $in_np = (new NPAModel())->is_in($bike_info['city_id'], $bike_info['coordinate'], 1);
                    if (!empty($in_np)) {
                        Log::debug("{$bike_info['id']} 禁停区 断电! acc=" . $bike_info['is_acc_on'], $in_np);
                    }
                } else {
                    Log::debug("{$bike_info['id']} 出服务区! acc=" . $bike_info['is_acc_on']);
                }

                if (empty($in_srv_area) || !empty($in_np)) {
                    Log::debug("##################### {$bike_info['id']} 出服务区! acc=" . $bike_info['is_acc_on']);

                    if ($bike_info['is_acc_on'] == 1) {
                        $bound_order_info = [
                            'order_type' => ORDER_TYPE_USER_RIDE,
                            'order_id' => $bike_info['last_order_id'],
                            'op' => '出服务区，关闭电门',
                        ];
                        $this->asyn_acc_off($bike_info['city_id'], $bike_info['id'], $bound_order_info);
                    } else {
                        $warnkey = "OUT_OF_SVCA_WARNING|" . $bike_info['id'];
                        $inc = hiredis::db()->incr($warnkey);
                        hiredis::db()->expire($warnkey, 30);

                        if ($inc < 4) {
                            $bound_order_info = [
                                'order_type' => ORDER_TYPE_USER_RIDE,
                                'order_id' => $bike_info['last_order_id'],
                                'op' => '出服务区警告',
                            ];

                            $this->asyn_device_voice($bike_info['city_id'], $bike_info['id'], ECU_VOICE_TYPE_ABOUT_TO_OSVC, $bound_order_info);
                        }
                    }
                }
                else {
                    if ($bike_info['is_acc_on'] == 0) {
                        $bound_order_info = [
                            'order_type' => ORDER_TYPE_USER_RIDE,
                            'order_id' => $bike_info['last_order_id'],
                            'op' => '进入服务区，恢复电门',
                        ];
                        $this->asyn_acc_on($bike_info['city_id'], $bike_info['id'], $bound_order_info);
                    }

                    $v = new vtimer();
                    $distance_to_svca = (new geovector())->point_polygon_short_distance($coordinate, $in_srv_area['coordinates']);

                    //hilog::debug('###################### far way from svca: ' . $distance_to_svca. ", cost:" . $v->cost());

                    $warnkey = "ABOUT_TO_OUT_OF_SVCA_WARNING|" . $bike_info['id'];
                    if ($distance_to_svca < 25) {
                        $inc = hiredis::db()->incr($warnkey);

                        if ($inc < 4) {
                            hiredis::db()->expire($warnkey, 30);
                            $bound_order_info = [
                                'order_type' => ORDER_TYPE_USER_RIDE,
                                'order_id' => $bike_info['last_order_id'],
                                'op' => '即将出服务区警告',
                            ];

                            $this->asyn_device_voice($bike_info['city_id'], $bike_info['id'], ECU_VOICE_TYPE_OSVC, $bound_order_info);
                        }
                    } else {
                        hiredis::db()->del($warnkey);
                    }
                }

            }

            if ($bike_info['bus_state'] == BIKE_BUS_STAT_RIDING || $bike_info['bus_state'] == BIKE_BUS_STAT_TEMP_PARK) {
                $item = null;
                $distance_incr = -1;
                $distance = -1;

                if (isset($info['coordinate'])) {
                    $item = [
                        floatval($info['coordinate'][0]),
                        floatval($info['coordinate'][1]),
                        $info['gps_update_time'],
                        intval($info['speed']),
                        intval($info['course']),
                    ];
                }

                if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
                    $distance = $info['trip_miles'];
                } else if (isset($info['coordinate'])) {
                    $distance_incr = common::distance($bike_info['coordinate'], $info['coordinate']);
                    if ($distance / (strtotime($info['gps_update_time']) - strtotime($bike_info['gps_update_time']) + 0.1) > 20) {
                        $distance_incr = 0;
                    }
                }

                (new UserRadingOrderModel())->append_track($bike_info['city_id'], $bike_info['last_order_id'], $item, $distance, $distance_incr);
            } else if ($bike_info['bus_state'] == BIKE_BUS_STAT_OPER_REPARK) {
                if (isset($info['coordinate'])) {
                    $item = [
                        floatval($info['coordinate'][0]),
                        floatval($info['coordinate'][1]),
                        $info['gps_update_time'],
                        intval($info['speed']),
                        intval($info['course']),
                    ];
                    (new ReparkOrderModel())->append_track($bike_info['city_id'], $bike_info['last_repark_order_id'], $item);
                }
            } else if ($bike_info['bus_state'] == BIKE_BUS_STAT_USER_REPARK) {
                if (isset($info['coordinate'])) {
                    $item = [
                        floatval($info['coordinate'][0]),
                        floatval($info['coordinate'][1]),
                        $info['gps_update_time'],
                        intval($info['speed']),
                        intval($info['course']),
                    ];
                    (new UserReparkOrderModel())->append_track($bike_info['city_id'], $bike_info['last_repark_order_id'], $item);
                }
            }
        }


        if (isset($info['is_on_battery']) && $bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE && $info['is_on_battery'] != $bike_info['is_on_battery']) {
            if ($info['is_on_battery']) {
                $bcdata = [
                    'battery_on_time' => date('Y-m-d H:i:s'),
                ];
                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $bcdata);
            } else {
                $bcdata = [
                    'battery_off_time' => date('Y-m-d H:i:s'),
                ];
                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $bcdata);
            }
        }
        if (isset($info['is_battery_locked']) && $bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE && $info['is_battery_locked'] != $bike_info['is_battery_locked']) {
            if ($info['is_battery_locked']) {
                $bcdata = [
                    'battery_lock_time' => date('Y-m-d H:i:s'),
                ];
                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $bcdata);
            } else {
                $bcdata = [
                    'battery_unlock_time' => date('Y-m-d H:i:s'),
                ];
                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $bcdata);
            }
        }

        if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE) {
            if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
                (new TBitEcuModel())->locate_rt($bike_info['ecu_sn']);
            }
        }

        if (isset($info['battery_volt'])) {
            $is_acc_on = -1;
            $accinfo = array_merge(
                [
                    'is_defend_on' => $bike_info['is_defend_on'],
                    'is_acc_on' => $bike_info['is_acc_on']
                ], $data);

            if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE || common::timeminus($update_time,$bike_info['defend_update_time']) > 30){
                if ($accinfo['is_defend_on'] == 0 && $accinfo['is_acc_on'] == 1) {
                    $is_acc_on = 1;
                } else if ($accinfo['is_defend_on'] == 1 && $accinfo['is_acc_on'] == 0) {
                    $is_acc_on = 0;
                }
            }

            if ($is_acc_on >= 0){
                list($battery_volt_avg_accoff, $battery_volt_avg_accon) = (new BikeBatteryCalc())->conv($bike_info['id'], $is_acc_on, $data['battery_volt']);
                $level = null;
                if ($bike_info['battery_model'] == 'TN48V20AH') {
                    $level = self::voltmv_to_percent_TN($battery_volt_avg_accoff, $battery_volt_avg_accon);
                }
                else if ($bike_info['battery_model'] == 'TN48V15AH'){
                    $level = self::voltmv_to_percent_TN15A($battery_volt_avg_accoff, $battery_volt_avg_accon);
                }
                else {
                    $battery_volt_avg = 0;
                    if ($battery_volt_avg_accon >= 0 && $battery_volt_avg_accoff >= 0) {
                        $battery_volt_avg = $battery_volt_avg_accoff * 0.2 + $battery_volt_avg_accon * 0.8;
                    } else {
                        $battery_volt_avg = max($battery_volt_avg_accoff, $battery_volt_avg_accon);
                    }

                    $level = self::voltmv_to_percent($info['battery_volt']);
                }

                $range = floor(53 * $level / 100);
                $info = array_merge($info, [
                    'battery_level' => $level,
                    'battery_range' => $range,
                    'battery_update_time' => date('Y-m-d H:i:s'),
                    'battery_capacity' => intval(140 * $level),
                    'battery_volt' => $data['battery_volt'],
                ]);
                if ($level < 10) {
                    $info['is_low_battery'] = 1;
                } else {
                    $info['is_low_battery'] = 0;
                }

                if ($info['battery_volt'] <= 10000) {
                    $info['is_under_volt'] = 1;
                } else {
                    $info['is_under_volt'] = 0;
                }

                if ($bike_info['is_low_battery'] > 0 && $info['is_low_battery'] == 0) {
                    $info['low_battery_scaned_count'] = 0;
                }

                if ($level > $bike_info['battery_level'] && common::timeminus($update_time,$bike_info['last_battery_change_order_start_time']) <3600){
                    (new BatteryChangeOrderModel())->fix_battery_level_after($bike_info['city_id'],$bike_info['last_battery_change_order_id'],$level);
                }
            }


        }

        $repark_index = 0;
        if ($bike_info['bus_state'] != BIKE_BUS_STAT_RIDING) {
            $park_time = strtotime($bike_info['park_update_time']);
            $now_time_h = date('H');
            $dif = floor((time() - $park_time) / 86400);

            $repark_index += $dif * 18;
            $park_time_h = date('H', $park_time);
            $s = $park_time_h;
            $e = $now_time_h;
            if ($e <= $s  && time() - $park_time > 3600) {
                $e = $e + 24;
            }
            for ($i = $s; $i < $e; $i++) {
                $h = $i % 24;
                if ($h < 7 || $h > 21) {
                    $repark_index += 0;
                } else {
                    $repark_index += 1;
                }
            }

            //hilog::debug($dif, $s, $e);

            $c = 1;
            if ($bike_info['park_state'] == BIKE_PARK_STAT_OSA) {
                if ($repark_index <= 0) {
                    $repark_index = 1;
                }
                $c = 5;
            } else if ($bike_info['park_state'] == BIKE_PARK_STAT_NPA) {
                if ($repark_index <= 0) {
                    $repark_index = 1;
                }
                $c = 3;
            } else if ($bike_info['park_state'] == BIKE_PARK_STAT_PARK) {
                $repark_index = 0;
            }
            $repark_index *= $c;

            // hilog::debug("############## {$bike_info['id']}, repark_index={$repark_index}");
            $repark_index = floor($repark_index / 3);
            if ($repark_index > 4) {
                $repark_index = 4;
            }
        }
        if ($bike_info['repark_index'] != $repark_index) {
            $info['repark_index'] = $repark_index;

            //预警
            $warn_time_h = date('H');
            if ($repark_index >= 4 && $warn_time_h >=8 && $warn_time_h < 22 &&$bike_info['is_warned'] == 0){
                //看看处理过没
                $info['last_warn_time'] = date('Y-m-d H:i:s');
                $info['is_warned'] = 1;

                (new BikeWarnModel())->add($bike_info);
            }
        }
        $bike_info = BikeModel::instance()->get_info_by_imei($imei);


        BikeModel::instance()->on_bike_info_update($bike_info['city_id'], $bike_info['id'], $info);

        if (!empty($info)) {
            //(new BikeTrackModelNEW())->add($bike_info['imei'], array_merge($bike_info, $info));
        }

        $this->check_alert($bike_info['id']);
    }

    function on_bluetooth_report($imei, $data, $timestamp) {
        // hilog::debug("callback imei", $imei, $coordinate);

        $bike_info = BikeModel::instance()->get_info_by_imei($imei);
        if (!empty($bike_info['update_time']) && strtotime($bike_info['update_time']) > $timestamp) {
            return;
        }

        $coordinate = (new CoordConv())->wgs84togcj02([$data['longitude'], $data['latitude']]);
        $info = [
            'gsm_signal_strength' => $data['gsm'],
            'sw' => $data['sw'],
        ];

        $swinfo = self::sw_to_info($data['sw']);
        unset($swinfo['is_defend_on']);

        $info = array_merge($info, $swinfo);

        $arr = [
            'gps_update_time' => date('Y-m-d H:i:s'),
            'coordinate' => $coordinate,
            'speed' => intval($data['speed']),
            'course' => intval($data['course']),
            'hdop' => intval($data['hdop']),
            'satellite' => intval($data['satellite']),
            'locate_type' => 'gps',
        ];

        $info = array_merge($info, $arr);

        $info['battery_volt'] = intval($data['voltage']);


        $this->on_bike_info_update($imei, $info);
    }

    function on_tbit_ecu_event_gps($ecu_sn, $data) {
        $timestamp = $data['timestamp'];

        $now = time();

        if ($timestamp > $now){
            Log::debug('timestamp error',$ecu_sn,$data);
            $timestamp = $now;
        }

        $gps = $data['gps'];
        $gsm = $data['gps']['gsm'];

        $bike_state = $data['state'];
        $info = [
            'gsm_signal_strength' => $gsm['gsm_signal_strength'],
            'update_time' => date('Y-m-d H:i:s', $timestamp),
            'satellite' => $gsm['satellite'],
            'is_online' => 1,
            'online_update_time' => date('Y-m-d H:i:s', $timestamp),
            'battery_volt' => intval($data['battery_volt']),
            'is_defend_on' => $bike_state['is_defend_on'],
            'is_acc_on' => $bike_state['is_acc_on'],
            'is_wheel_locked' => $bike_state['is_wheel_locked_acc'],
            'is_sleeping' => $bike_state['is_sleeping'],
            'is_in_motion' => $bike_state['is_in_motion'],
            'trip_miles' => $data['trip_miles'],
        ];

        $gps_info = null;

        if ($gsm['gps_flag'] === 1 || $gsm['gps_flag'] === 2) {
            $coordinate = (new CoordConv())->wgs84togcj02([$gps['lon'], $gps['lat']]);
            if (abs($coordinate[0]) > 0.000001 && abs($coordinate[1]) > 0.000001) {
                $gps_info = [
                    'gps_update_time' => date('Y-m-d H:i:s'),
                    'coordinate' => $coordinate,
                    'speed' => intval($gps['speed']),
                    'course' => intval($gps['course']),
                    'hdop' => intval($gps['ltitude']),
                    'satellite' => intval($gsm['satellite']),
                    'locate_type' => 'gps',
                ];
            }
        }

        $ecu_info = $info;
        if (!empty($gps_info)) {
            $ecu_info = array_merge($info, $gps_info);
        }

        (new BikeTrackModelNEW())->add($ecu_sn, $ecu_info);

        $bike_info = BikeModel::instance()->get_info_by_ecu_sn($ecu_sn);
        if (empty($bike_info)) {
            Log::debug('query bike info error', $ecu_sn);
            return -1;
        }

        if (!empty($bike_info['update_time']) && strtotime($bike_info['update_time']) > $timestamp) {
            Log::debug('gps_event_overdue', $ecu_sn, $data);
            return -1;
        }

        $now = time();
        if ($timestamp > $now) {
            Log::debug('timestamp error', $ecu_sn, $data);
            return -1;
        }

        if (!empty($gps_info)) {
            if (!empty($bike_info['gps_update_time']) || strtotime($bike_info['gps_update_time'] < $timestamp)) {
                $info = array_merge($info, $gps_info);
            }
        }

        $this->on_bike_info_update($bike_info['imei'], $info);
        return 0;
    }

    function on_xiaoan_ecu_event_gps($imei, $data, $timestamp, $bluetooth) {
        $coordinate = (new CoordConv())->wgs84togcj02([$data['longitude'], $data['latitude']]);
        $info = [
            'gsm_signal_strength' => $data['gsm'],
            'sw' => $data['sw'],
            'update_time' => date('Y-m-d H:i:s', $timestamp),
        ];


        if (!$bluetooth) {
            $info['is_online'] = 1;
            $info['online_update_time'] = date('Y-m-d H:i:s');
        }

        $swinfo = self::sw_to_info($data['sw']);

        $info = array_merge($info, $swinfo);
        $ecu_info = $info;

        $gps_info = null;
        if (abs($coordinate[0]) > 0.000001 && abs($coordinate[1]) > 0.000001) {
            $gps_info = [
                'gps_update_time' => date('Y-m-d H:i:s'),
                'coordinate' => $coordinate,
                'speed' => intval($data['speed']),
                'course' => intval($data['course']),
                'hdop' => intval($data['hdop']),
                'satellite' => intval($data['satellite']),
                'locate_type' => 'gps',
            ];
        }

        if (!empty($gps_info)) {
            $ecu_info = array_merge($ecu_info, $gps_info);
        }

        (new BikeTrackModelNEW())->add($imei, $ecu_info);

        $bike_info = BikeModel::instance()->get_info_by_imei($imei);
        if (empty($bike_info)) {
            Log::debug('query bike info error', $imei);
            return -1;
        }

        if (!empty($bike_info['update_time']) && strtotime($bike_info['update_time']) > $timestamp) {
            Log::debug('gps_event_overdue', $bike_info['id']);
            return -1;
        }

        if (!empty($gps_info) && !empty($info)) {
            if (empty($bike_info['gps_update_time']) || strtotime($bike_info['gps_update_time']) < $data['timestamp']) {
                $info = array_merge($info, $gps_info);
            }
        }

        $info['battery_volt'] = intval($data['voltage']);

        $this->on_bike_info_update($imei, $info);
        return 0;
    }

    function on_event_notify($imei, $notify_type) {
        // hilog::debug('on_event_nofity', $imei, $notify_type);
        if ($notify_type == 6) {//电池移除
            $bike_info = BikeModel::instance()->get_info_by_imei($imei);
            if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE) {
                $data = [
                    'battery_off_time' => date('Y-m-d H:i:s'),
                ];
                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $data);
            }

            $data = [
                'is_on_battery' => 0,
                'battery_update_time' => date('Y-m-d H:i:s'),
            ];
            BikeModel::instance()->on_bike_info_update($bike_info['city_id'], $bike_info['id'], $data);
            $this->on_bike_info_update($imei, $data);

            //(new BikeBatteryCalc())->reset($bike_info['id']);
        } else if ($notify_type == 23) { //电池恢复
            $bike_info = BikeModel::instance()->get_info_by_imei($imei);
            if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE) {
                $data = [
                    'battery_on_time' => date('Y-m-d H:i:s'),
                ];
                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $data);
            }

            $data = [
                'is_on_battery' => 1,
                'battery_update_time' => date('Y-m-d H:i:s'),
            ];
            $this->on_bike_info_update($imei, $data);
            //(new BikeBatteryCalc())->reset($bike_info['id']);
        } else if ($notify_type == 11) { //关电池锁
            $bike_info = BikeModel::instance()->get_info_by_imei($imei);
            if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE) {
                $data = [
                    'battery_lock_time' => date('Y-m-d H:i:s'),
                ];

                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $data);
                $ret = [
                    0, 'ok', 'ok',
                ];
                $bound_order_info = [
                    'order_type' => ORDER_TYPE_BIKE_BATTERY_CHANGE,
                    'order_id' => $bike_info['last_battery_change_order_id'],
                    'op' => '关电池锁',
                ];

                (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_BATTERY_LOCK, $bike_info['city_id'], $bike_info['id'], $bound_order_info, $ret);
            } else {

                $ret = [
                    0, 'ok', 'ok',
                ];

                $bound_order_info = [
                    'order_type' => '',
                    'order_id' => '',
                    'op' => '关电池锁',
                ];
                (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_BATTERY_LOCK, $bike_info['city_id'], $bike_info['id'], $bound_order_info, $ret);
            }
            $data = [
                'is_battery_locked' => 1,
                'battery_update_time' => date('Y-m-d H:i:s'),
            ];
            BikeModel::instance()->on_bike_info_update($bike_info['city_id'], $bike_info['id'], $data);
        } else if ($notify_type == 12) { //开电池锁
            $bike_info = BikeModel::instance()->get_info_by_imei($imei);
            if ($bike_info['bus_state'] == BIKE_BUS_STAT_BATTERY_CHANGE) {
                $data = [
                    'battery_unlock_time' => date('Y-m-d H:i:s'),
                ];

                (new BatteryChangeOrderModel())->update_info($bike_info['city_id'], $bike_info['last_battery_change_order_id'], $data);
            }
            $data = [
                'is_battery_locked' => 1,
                'battery_update_time' => date('Y-m-d H:i:s'),
            ];
            BikeModel::instance()->on_bike_info_update($bike_info['city_id'], $bike_info['id'], $data);
        } else if ($notify_type == 4 || $notify_type == 10) {
            $data = [
                'is_online' => 0,
                'online_update_time' => date('Y-m-d H:i:s'),
            ];

            $this->on_bike_info_update($imei, $data);
        } else if ($notify_type == 3) {
            $data = [
                'is_online' => 1,
                'online_update_time' => date('Y-m-d H:i:s'),
            ];

            $this->on_bike_info_update($imei, $data);
        }
    }

    static function sw_to_info($sw) {
        $info = [
            'is_defend_on' => $sw & (1 << 0) ? 1 : 0,
            'is_acc_on' => $sw & (1 << 1) ? 1 : 0,
            'is_wheel_locked' => $sw & (1 << 2) ? 0 : 1,
            'is_battery_locked' => $sw & (1 << 3) ? 1 : 0,
            'is_on_battery' => $sw & (1 << 4) ? 1 : 0,
            'is_in_motion' => $sw & (1 << 6) ? 1 : 0,
            'is_wheel_span' => $sw & (1 << 7) ? 1 : 0,
            'is_sleeping' => $sw & (1 << 9) ? 1 : 0,
        ];
        if ($info['is_wheel_span']) {
            $info['is_in_motion'] = 1;
        }
        return $info;
    }

    function battery_unlock($city_id, $bike_id, $bound_order_info) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        $ret = [-1, "未知设备型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $ret = (new TBitEcuModel())->unlock_battery($bike_info['ecu_sn']);
        } else {
            $ret = (new XiaoAnEcuModel())->unlock_battery($bike_info['imei']);
        }
        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_BATTERY_UNLOCK, $city_id, $bike_id, $bound_order_info, $ret);
        return $ret;
    }

    function maintain($city_id, $bike_id) {
        (new BikeModel())->maintain($bike_id);
        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_MAINTAIN, $city_id, $bike_id, null, null);
    }

    function check($city_id, $bike_id) {
        (new BikeModel())->check($bike_id);
        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_CHECK, $city_id, $bike_id, null, null);
    }

    function user_ride_violation_report($city_id, $bike_id, $violation_type, $violation_remark, $violation_desc, $images) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        if (empty($bike_info)) {
            throw_myexception(ERROR_QUERY_INFO, "query bike info error");
        }

        $info = (new UserRideViolationOrderModel())->submit($bike_info, $violation_type, $violation_desc, $violation_remark, $images);
        return $info;
    }

    function auto_handle_alert($city_id, $bike_id, $alert_type_arr) {
        $bike_info = BikeModel::instance()->get_info_by_id($bike_id);
        if ($bike_info['alert_state'] == 0)
            return;

        $arr = [];
        foreach ($alert_type_arr as $type) {
            if (($bike_info['alert_state'] & $type) > 0) {
                $arr[] = $type;
                BikeModel::instance()->unset_alert($city_id, $bike_id, $type);
            }
        }

        if (empty($arr)) {
            return;
        }

        $condition = [
            'bike_id' => $bike_id,
            'is_solved' => 0,
            'types' => $arr,
        ];

        $alerts = (new BikeAlertModel())->get_list($city_id, $condition, 0, 200);
        $alert_flag = 0;
        foreach ($alerts as $alert_info) {
            if ($bike_info['alert_state'] & $alert_info['type'] && in_array($alert_info['type'], $arr)) {
                $alert_flag = $alert_flag | $alert_info['type'];
                (new BikeAlertModel())->handle($city_id, $alert_info['id'], BIKE_ALERT_RESULT_SOLVED, 'AUTOHANLED');
            }
        }
    }

    function asyn_grasproad_track($city_id, $order_id) {
        $params = [
            'city_id' => $city_id,
            'order_id' => $order_id,
        ];

        TimerALModel::instance()->call_bus_server('/urorder/grasproad_track', $params, 0);
    }

    function device_voice($imei, $voice_type, $bound_order_info) {
        $bike_info = BikeModel::instance()->get_info_by_imei($imei);
        if (empty($bike_info)) {
            return;
        }

        $result = [-1, "未知型号", 0];
        if ($bike_info['ecu_model'] == ECU_MODEL_TBIT_209D) {
            $map = [
                ECU_VOICE_TYPE_RING => TBitEcuModel::TBIT_VOICE_TYPE_RING,
                ECU_VOICE_TYPE_ABOUT_TO_OSVC => TBitEcuModel::TBIT_VOICE_TYPE_OSVC,
                ECU_VOICE_TYPE_OSVC => TBitEcuModel::TBIT_VOICE_TYPE_OSVC_ALEADY,
                ECU_VOICE_TYPE_PARKWARN =>TBitEcuModel::TBIT_VOICE_TYPE_PARKWARN,
                ECU_VOICE_TYPE_OVERSPEED => TBitEcuModel::TBIT_VOICE_TYPE_OVERSPEED,
            ];
            $t = 0;
            if (!isset($map[$voice_type])) {
                $t = 0;
            } else {
                $t = $map[$voice_type];
            }
            $result = (new TBitEcuModel())->device_voice($bike_info['ecu_sn'], $t);
        } else {
            $map = [
                ECU_VOICE_TYPE_RING => XiaoAnEcuModel::XIAO_AN_VOICE_TYPE_RING,
                ECU_VOICE_TYPE_ABOUT_TO_OSVC => XiaoAnEcuModel::XIAO_AN_VOICE_TYPE_OSVC,
                ECU_VOICE_TYPE_OSVC => XiaoAnEcuModel::XIAO_AN_VOICE_TYPE_OSVC_ALEADY,
                ECU_VOICE_TYPE_PARKWARN =>XiaoAnEcuModel::XIAO_AN_VOICE_TYPE_PARKWARN,
                ECU_VOICE_TYPE_OVERSPEED => XiaoAnEcuModel::XIAO_AN_VOICE_TYPE_OVERSPEED,
            ];
            $t = 0;
            if (!isset($map[$voice_type])) {
                $t = 0;
            } else {
                $t = $map[$voice_type];
            }
            $result = (new XiaoAnEcuModel())->device_voice($imei, $t);
        }

        (new BikeOperRecordModel())->add(BIKE_OPER_TYPE_VOICE, $bike_info['city_id'], $bike_info['id'], $bound_order_info, $result);

        return $result;
    }


    function asyn_warn_info($com_staff_info) {
        TimerALModel::instance()->call_bus_server('/bike/complete_warn', $com_staff_info, 0);
    }
}

class BikeBatteryCalc {

    static function avg($arr) {
        $len = count($arr);

        if ($len <= 3) {
            return array_sum($arr) / $len;
        }
        sort($arr);

        $splice = floor($len / 4);
        $sum = 0;
        for ($i = $splice; $i < count($arr) - $splice; $i++) {
            $sum += $arr[$i];
        }
        return $sum / ($len - $splice * 2);
    }

    function conv($bike_id, $is_acc_on, $mv) {
        $KEY = "BIKE_BATTERY_VOLT_ZSETV4|{$bike_id}";
        $vals = hiredis::db()->zRange($KEY, 0, -1, true);
        $fk = null;

        $nowtimestamp = time();
        $reset = false;
        $map_acc_on = [];
        $map_acc_off = [];
        if ($is_acc_on) {
            $map_acc_on = [$mv];
        } else {
            $map_acc_off = [$mv];
        }
        foreach ($vals as $v => $k) {
            if ($nowtimestamp - $k > 300) continue;
            if ($v > 0) {
                $map_acc_on[] = $v;
            } else {
                $v = 0 - $v;
                $map_acc_off[] = $v;
            }

            if ($v == 0 || abs($mv - $v) / $v > 0.2) {
                $reset = true;
                break;
            }
        }

        $timestamp_to_delete = $nowtimestamp - 300;
        if ($reset) {
            if ($is_acc_on) {
                $map_acc_on = [$mv];
            } else {
                $map_acc_off = [$mv];
            }
            $timestamp_to_delete = $nowtimestamp;
        }

        hiredis::db()->zDeleteRangeByScore($KEY, 0, $timestamp_to_delete);
        hiredis::db()->zAdd($KEY, $nowtimestamp, $mv * ($is_acc_on * 2 - 1));

        $avg_off = empty($map_acc_off) ? -1 : self::avg($map_acc_off);
        $avg_on = empty($map_acc_on) ? -1 : self::avg($map_acc_on);
        Log::debug('################### CONV', $avg_off, $avg_on, $map_acc_off, $map_acc_on, $vals);
        return [$avg_off, $avg_on];
    }

    function reset($bike_id) {
        $KEY = "BIKE_BATTERY_VOLT_QUEUE|" . $bike_id;
        hiredis::db()->del($KEY);
    }

}
